Skip to main content

Linked List

A linked list is a fundamental data structure that represents a sequence of elements. Each element, known as a node, contains:

  • Data (Value): The information stored in the node.
  • Reference (Pointer): A link to the next node in the sequence.

The first node is called the head, and the last node is the tail, which points to None, indicating the end of the list.

Node Structure in Python

Here's an example of a simple node class for a singly linked list:

class ListNode:
"""Node class for a singly linked list."""
def __init__(self, val: int):
self.val: int = val # Data stored in the node
self.next: 'ListNode' = None # Reference to the next node

Creating a Linked List

To build a linked list, instantiate nodes and link them using their next references:

# Create nodes with values
n0 = ListNode(1)
n1 = ListNode(3)
n2 = ListNode(2)
n3 = ListNode(5)
n4 = ListNode(4)

# Link nodes: 1 -> 3 -> 2 -> 5 -> 4
n0.next = n1
n1.next = n2
n2.next = n3
n3.next = n4

# Set the head of the list
head = n0

Common Operations on Linked Lists

Time Complexity Comparison

OperationArraySingly Linked ListDoubly Linked ListCircular Linked List
Access by IndexO (1)O (n)O (n)O (n)
Insertion at HeadO (n)O (1)O (1)O (1)
Deletion at HeadO (n)O (1)O (1)O (1)
Insertion at TailO (1)*O (n)**O (1)O (1)
Deletion at TailO (1)*O (n)O (1)O (1)
Search by ValueO (n)O (n)O (n)O (n)

* If the array has unused capacity at the end.

** O (1) if a tail reference is maintained.

Inserting a Node

To insert a node P after a node n0:

def insert(n0: ListNode, P: ListNode):
"""Insert node P after node n0 in the linked list."""
P.next = n0.next
n0.next = P

Deleting a Node

To remove the node immediately after n0:

def remove(n0: ListNode):
"""Remove the node after node n0 in the linked list."""
if n0.next:
n0.next = n0.next.next

Accessing a Node by Index

To access the node at a specific index:

def access(head: ListNode, index: int) -> ListNode | None:
"""Return the node at the specified index."""
current = head
for _ in range(index):
if current is None:
return None
current = current.next
return current

Searching for a Node by Value

To find the first node with a given value:

def find(head: ListNode, target: int) -> int:
"""Return the index of the node with the target value."""
current = head
index = 0
while current:
if current.val == target:
return index
current = current.next
index += 1
return -1 # Target not found

Arrays vs. Linked Lists

Key Differences

FeatureArraysLinked Lists
Memory AllocationContiguousNon-contiguous
SizeFixed (static)Dynamic
Memory OverheadMinimalExtra for pointers
Access TimeO (1) (random access)O (n) (sequential access)
Insertion/DeletionO (n) (requires shifting)O (1) (if node is known)
Cache PerformanceBetter (due to locality)Poorer

Types of Linked Lists

Singly Linked List

  • Structure: Nodes with a single next reference.
  • Traversal: Forward only.

Doubly Linked List

  • Structure: Nodes with next and prev references.

  • Traversal: Forward and backward.

  • Node Class Example:

    class ListNode:
    """Node class for a doubly linked list."""
    def __init__(self, val: int):
    self.val: int = val
    self.next: 'ListNode' = None
    self.prev: 'ListNode' = None

Circular Linked List

  • Structure: The last node points back to the first node.
  • Use Cases: Useful for applications requiring cyclic traversal.

Advantages and Disadvantages

Advantages

  • Dynamic Size: Adjusts during runtime as elements are added or removed.
  • Efficient Insertions/Deletions: O (1) time when inserting or deleting at known positions.
  • Flexible Memory Use: Utilizes memory as needed without pre-allocation.

Disadvantages

  • Memory Overhead: Additional memory for storing references.
  • No Random Access: Cannot directly access elements by index.
  • Cache Inefficiency: Non-contiguous memory allocation can lead to cache misses.
  • Complexity: More complex implementation compared to arrays.

Applications of Linked Lists

Singly Linked Lists

  • Stacks and Queues: Implementing LIFO and FIFO structures.
  • Hash Tables: Handling collisions using chaining.
  • Adjacency Lists: Representing sparse graphs.

Doubly Linked Lists

  • LRU Caches: Efficiently updating and accessing recently used items.
  • Text Editors: Managing the sequence of characters for efficient insertions and deletions.
  • Browser Navigation: Implementing back and forward navigation.

Circular Linked Lists

  • Round-Robin Scheduling: Distributing resources evenly across processes.
  • Audio/Video Buffers: Implementing continuous playback loops.
  • Multiplayer Games: Managing players in turn-based games.